#include "StdAfx.h"
#include "VectorSketch.h"
#include "ContourDot.h"
#include "Calculator.h"
#include "Curve.h"
#include "NeighborEnumerator.h"
#include "StrokeVertexesFinder.h"
#include "Utilities.h"
#include <vector>

#include <GdiPlus.h>
//using namespace Gdiplus;

//////////////////////////////////////////////////////
// Constants
const Gdiplus::ARGB kRegularColor = 0xFF00FF00;
const Gdiplus::ARGB kSelectionColor = 0xFFFF11AA;
const Gdiplus::ARGB kBlackColor = 0xFF000000;
const Gdiplus::ARGB kCurveColor = 0xFF0011FF;

// colors of the circles around the distinctive points
const Gdiplus::ARGB kDistinctivePointColorGreen = 0xFF11EE99; // ends intersection
const Gdiplus::ARGB kDistinctivePointColorYellow = 0xFFFFEE00; // angles
const Gdiplus::ARGB kDistinctivePointColorBrown = 0xFFBB6528; // T-points

const Gdiplus::ARGB kJunctionColor = 0xFFBB65AA; // junction

VectorSketch::VectorSketch(float inWidth, float inHeight)
	:	mWidth(inWidth),
		mHeight(inHeight),
		mGdiplusToken(NULL)
{

	// Append the new trial stroke. This is done for performance reason:
	// there will be multitude cases of strokes that fail initializing;
	// no need to reallocate memory before the next trial.
	mStrokes.push_back(Stroke());

	mIsFinal = false;
	mHighlightedStrokeIndex = kInvalidIndex;

	mShowAllStrokes = false;
	mShowDotOuterShapes = false;
	mShowDotInnerShapes = false;
	mShowAllThickCurves = false;
	mShowAllDistinctivePoints = false;

	Gdiplus::GdiplusStartupInput gdiplusInput;
	Gdiplus::GdiplusStartup(&mGdiplusToken, &gdiplusInput, NULL);
}


VectorSketch::~VectorSketch()
{
	mStrokes.clear();
	if (NULL != mGdiplusToken)
	{
		Gdiplus::GdiplusShutdown(mGdiplusToken);
	}
}

void VectorSketch::TraceNewStroke(int inX, int inY, Calculator& ioCalculator)
{
	Stroke& newStroke = mStrokes.back();
	if (newStroke.Initialize(inX, inY, ioCalculator))
	{
		// append the next trial stroke
		mStrokes.push_back(Stroke());

		ioCalculator.UpdateBooking(kOccupiedByOthersMask);
	}
}

void VectorSketch::FinalizeStrokes(Calculator& ioCalculator)
{
	// delete the trial stroke
	if (!mStrokes.empty())
	{
		mStrokes.erase(mStrokes.end() - 1);
	}

	float maximumBrightness = 0;
	for (StrokeCollection::iterator i = mStrokes.begin();
		i != mStrokes.end(); ++i)
	{	
		i->Finalize(ioCalculator);
		const float brightness = i->AverageBrightness();
		if (brightness > maximumBrightness)
		{
			maximumBrightness = brightness;
		}
	}
	mMaximumStrokeBrightness = maximumBrightness;
}

void VectorSketch::Finalize(Calculator& ioCalculator)
{
	mIsFinal = true;
}

void VectorSketch::AimTheDistinctivePoints(float inDistanceCriterion)
{
	NeighborEnumerator enumerator(mWidth, mHeight, inDistanceCriterion,
		2 * mStrokes.size());
	for (StrokeCollection::const_iterator it = mStrokes.begin();
		it != mStrokes.end(); ++it)
	{
		it->RegisterEndDots(enumerator);
	}

	StrokeVertexesFinder finder(enumerator);
	for (StrokeCollection::const_iterator it = mStrokes.begin();
		it != mStrokes.end(); ++it)
	{
		finder.AddVertexesForStrokeEnds(*it);
	}

	for (StrokeCollection::const_iterator it = mStrokes.begin();
		it != mStrokes.end(); ++it)
	{
		finder.AddVertexesForStroke(*it);
	}
	finder.PruneMultipleIntersections();

	mDistinctivePoints.resize(finder.GetNumberOfVertexes());
	if (!mDistinctivePoints.empty())
	{
		finder.GetVertexes(&mDistinctivePoints[0]);
	}
	mJunctions.resize(finder.GetNumberOfJunctions());
	if (!mJunctions.empty())
	{
		finder.GetJunctions(&mJunctions[0]);
	}
}

bool VectorSketch::IsFinal() const
{
	return mIsFinal;
}

void VectorSketch::GetStatistics(std::wostringstream& output) const
{
	int totalContourDots = 0;
	int numberOfLongStrokes = 0;
	for (StrokeCollection::const_iterator it = mStrokes.begin();
		it != mStrokes.end(); ++it)
	{
		int n = it->GetDotCount();
		totalContourDots += n;
		if (2 < n)
		{
			++numberOfLongStrokes;
		}
	}

	output << "Number of contour dots: " << totalContourDots
		<< std::endl;
	output << "Number of strokes: " << mStrokes.size() << std::endl;
	output << "Number of strokes longer than two dots: "
		<< numberOfLongStrokes << std::endl;
	output << "Number of distinctive points: " << mDistinctivePoints.size()
		<< std::endl;
	output << "Number of junctions: " << mJunctions.size() << std::endl;
}

void VectorSketch::GetHighlightedInfo(std::wostringstream& output) const
{
	if (kInvalidIndex != mHighlightedStrokeIndex)
	{
		const Stroke& stroke = mStrokes[mHighlightedStrokeIndex];
		output << "stroke #" << mHighlightedStrokeIndex << " ";
		stroke.GetHighlightedInfo(output);
	}
}

void VectorSketch::Draw(HDC inHdc, RECT inRect, 
	float inScale, float inShiftX, float inShiftY) const
{
	if (!mIsFinal)
	{
		return;
	}
	bool shouldDrawStrokes = !mStrokes.empty() &&
		(IsDrawingAllStrokes() || kInvalidIndex != mHighlightedStrokeIndex);
	bool shouldDrawDistinctiveDots = !mDistinctivePoints.empty() &&
		mShowAllDistinctivePoints;

	if (!shouldDrawStrokes && !shouldDrawDistinctiveDots)
	{
		return;
	}

	Gdiplus::Graphics graphics(inHdc);
	Gdiplus::Status status = graphics.GetLastStatus();
	Gdiplus::Status status1 = graphics.ScaleTransform(inScale, inScale);
	Gdiplus::Status status2 = graphics.TranslateTransform(inShiftX, inShiftY);

	if (Gdiplus::Ok != status ||
		Gdiplus::Ok != status1 ||
		Gdiplus::Ok != status2)
	{ 
		return;
	}
	graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

	Gdiplus::RectF rect(float(inRect.left) / inScale, 
		mHeight - float(inRect.bottom) / inScale,
		float(inRect.right - inRect.left) / inScale,
		float(inRect.bottom - inRect.top) / inScale);

	if (mShowAllThickCurves)
	{
		DrawThickCurves(graphics, rect);
	}

	Gdiplus::Color regularColor(kRegularColor);
	Gdiplus::Color selectionColor(kSelectionColor);

	Gdiplus::SolidBrush regularBrush(regularColor);
	Gdiplus::SolidBrush selectionBrush(selectionColor);

	Gdiplus::Pen blackPen(Gdiplus::Color(kBlackColor), 1 / inScale);
	Gdiplus::Pen regularCurvePen(Gdiplus::Color(kCurveColor), 1 / inScale);
	Gdiplus::Pen selectedCurvePen(selectionColor, 1 / inScale);

	// radius of the dots will not be changed with scaling.
	float centerRadius = 2.0f / inScale;
	bool showShapes = mShowDotOuterShapes | mShowDotInnerShapes;

	if (mShowAllStrokes)
	{
		for (int i = mStrokes.size(); i--;)
		{
	
			const Stroke& stroke = mStrokes[i];
			if (stroke.IsIntersecting(rect, showShapes))
			{
				if (mShowSideColors)
				{
					stroke.DrawDotSideColors(graphics);
				}	
				if (mShowCurves)
				{
					Gdiplus::Pen *pen = i == mHighlightedStrokeIndex ?
						&selectedCurvePen : &regularCurvePen;
					stroke.DrawCurve(graphics, pen);
				}

				if (mShowDotCenters)
				{
					Gdiplus::SolidBrush* brush = i == mHighlightedStrokeIndex ?
						&selectionBrush : &regularBrush;
					stroke.DrawDotCenters(graphics, brush, &blackPen, centerRadius);
				}

				if (i == mHighlightedStrokeIndex)
				{
					if (mShowDotOuterShapes)
					{
						stroke.DrawDotRects(graphics, &blackPen);
					}
					if (mShowDotInnerShapes)
					{
						stroke.DrawDotInnerShapes(graphics, &blackPen);
					}
				}
			}
		}
	}
	else if (kInvalidIndex != mHighlightedStrokeIndex)
	{
		const Stroke& stroke = mStrokes[mHighlightedStrokeIndex];
		if (stroke.IsIntersecting(rect, showShapes))
		{
			if (mShowSideColors)
			{
				stroke.DrawDotSideColors(graphics);
			}			
			if (mShowCurves)
			{
				stroke.DrawCurve(graphics, &selectedCurvePen);
			}
			if (mShowDotCenters)
			{
				stroke.DrawDotCenters(graphics, &selectionBrush, &blackPen, centerRadius);
			}
			if (mShowDotOuterShapes)
			{
				stroke.DrawDotRects(graphics, &blackPen);
			}
			if (mShowDotInnerShapes)
			{
				stroke.DrawDotInnerShapes(graphics, &blackPen);
			}
		}
	}

	if (shouldDrawDistinctiveDots)
	{
		DrawVertexes(graphics, rect, 1.0f / inScale);
		DrawJunctions(graphics, rect, 1.0f / inScale);
	}
}

bool VectorSketch::HighlightStroke(float inX, float inY, 
	float inProximity, RECT &outRect)
{
	if (!mIsFinal)
		return false;
	
	POINT pt;
	pt.x = LONG(inX); pt.y = LONG(inY);
	int foundIndex = kInvalidIndex;
	bool selectedDotChanged = false;

	for (int i = mStrokes.size(); i--;)
	{
		Stroke& stroke = mStrokes[i];
		if (!PtInRect(&stroke.GetBounds(false), pt))
		{
			continue;
		}

		bool selectionChanged = false;
		if (stroke.FindNearestDot(inX, inY, inProximity, selectedDotChanged))
		{
			foundIndex = i;
			break;
		}
	}

	if (foundIndex == mHighlightedStrokeIndex)
	{
		if (kInvalidIndex != foundIndex && selectedDotChanged)
		{
			outRect = mPrevSelectionRect;
		}
		return selectedDotChanged;
	}

	if (kInvalidIndex != foundIndex)
	{
		const RECT& r = mStrokes[foundIndex].GetBounds(mShowDotOuterShapes | mShowDotInnerShapes);
		if (kInvalidIndex == mHighlightedStrokeIndex)
		{
			outRect = r;
		}
		else
		{
			::UnionRect(&outRect, &r, &mPrevSelectionRect);
		}
		mPrevSelectionRect = r;
	}
	else
	{
		outRect = mPrevSelectionRect;
	}

	if (kInvalidIndex != mHighlightedStrokeIndex)
	{
		mStrokes[mHighlightedStrokeIndex].UnselectNearestDot();
	}

	mHighlightedStrokeIndex = foundIndex;

	return true;
}

void VectorSketch::SetParameter(int inKey, bool inValue)
{
	switch (inKey)
	{
	case kShowCurves: mShowCurves = inValue; break;
	case kShowDotCenters: mShowDotCenters = inValue; break;
	case kShowDotOuterShapes: mShowDotOuterShapes = inValue; break;
	case kShowDotInnerShapes: mShowDotInnerShapes = inValue; break;
	case kShowSideColors: mShowSideColors = inValue; break;
	case kShowAllStrokes: mShowAllStrokes = inValue; break;
	case kShowAllThickCurves: mShowAllThickCurves = inValue; break;
	case kShowAllDistinctivePoints: mShowAllDistinctivePoints = inValue; break;
	}
}

bool VectorSketch::IsDrawingAllStrokes() const
{
	return mShowAllStrokes || mShowAllThickCurves;
}

//-------------------------------------------------------------------------
void VectorSketch::DrawVertexes(Gdiplus::Graphics& ioGraphics, 
	const Gdiplus::RectF& inRect, float inLineWidth) const
{
	Gdiplus::Pen greenPen(Gdiplus::Color(kDistinctivePointColorGreen),
		inLineWidth);
	Gdiplus::Pen yellowPen(Gdiplus::Color(kDistinctivePointColorYellow),
		inLineWidth);
	Gdiplus::Pen brownPen(Gdiplus::Color(kDistinctivePointColorBrown),
		inLineWidth);

	for (DistinctivePointCollection::const_iterator it = mDistinctivePoints.begin();
		it != mDistinctivePoints.end(); ++it)
	{
		Gdiplus::Pen* penPtr = &greenPen;
		if (it->IsTPoint())
		{
			penPtr = &brownPen;
		}
		else if (it->IsCorner())
		{
			penPtr = &yellowPen;
		}
		it->Draw(ioGraphics, *penPtr, inRect);
	}
}

void VectorSketch::DrawJunctions(Gdiplus::Graphics& ioGraphics,
	const Gdiplus::RectF& inRect, float inLineWidth) const
{
	Gdiplus::Pen pen(Gdiplus::Color(kJunctionColor), inLineWidth);

	for (JunctionCollection::const_iterator it = mJunctions.begin();
		it != mJunctions.end(); ++it)
	{
		const ContourDot* endDots[2];
		BOOL isVisible = false;
		for (int i = 0; i < 2; ++i)
		{
			const ContourDot* dot = it->strokes[i]->GetContourDotAt(
				it->strokeEnds[i]);
			endDots[i] = dot;
			isVisible |= inRect.Contains(dot->GetX(), dot->GetY());
		}
		if (!isVisible)
		{
			continue;
		}

		float v1x = endDots[0]->GetVx();
		float v1y = endDots[0]->GetVy();
		if (Stroke::kBeforeFront == it->strokeEnds[0])
		{
			v1x = -v1x;
			v1y = -v1y;
		}

		Curve curve(endDots[0]->GetX(), endDots[0]->GetY(), v1x, v1y,
			it->strokes[1], it->strokeEnds[1]);
		Gdiplus::GraphicsPath path;
		curve.AddToPath(&path);
		ioGraphics.DrawPath(&pen, &path);
	}
}